Biblioteczka Calibre w chmurze

Biblioteczka Calibre w chmurze

Była sobie biblioteczka calibre online, dostęp działał, dało się przeglądać na knedlu i pobierać książki bezpośrednio na czytnik. Skoro wszystko działało, to co się zesrauo?

Krótko mówiąc, ilość miejsca na serwerze nie jest z gumy a storage staje się coraz droższy.

Jak to się zaczęło i na co komu cały ambaras?

Otóż dobiegał końca czas ważności konta hostingowego, a ceny usług hostingowych ostatnimi czasy nie stały w miejscu. Związane jest to oczywiście z konsolidacją tego rynku, przejęciami wielu mniejszych marek przez pazerne korpo, co kończy się podniesieniem cen i to kilkukrotnie. Istniejący zawodnicy, którzy nie sprzedali duszy diabłu również musieli zaktualizować cenniki, gdy cena prądu rośnie jak garb od roboty a w tym kraju prąd - nie wiedzieć czemu - już i tak wyjątkowo drogi.

Po szybkim rekonesansie na polu krajowego webhostingu i poszukiwaniu czegoś z dostępem po SSH, za co nie będę musiał uiszczać dodatkowych opłat, padło na budżetową usługę od załogi MyDevil. Znanych przez webdeveloperów, o ugruntowanej pozycji blablablapierdolety. Budżetową to znaczy pisiont rocznie to max ile wysupłam ciężko zarobionych w pocie i znoju polskich nowych.

I gardło sobie przy tym podrzynam!Geronimo Sebastian Procjusz Dibbler

5GB w zupełności wystarczy na tę resztę cyfrowych śmieci jakie jeszcze mam. Większość i tak zajmowała biblioteczka calibre, zabierając niespełna 2GB quoty dyskowej.

W momencie gdy zamierzałem zrobić synchro biblioteczki zadałem sobie pytanie:

na H mam trzymać to wszystko na swoim koncie hostingowym, skoro kopię trzymam w chmurze?

Od pytania do działania upłynęło trochę czasu, bo usecase choć wydaje się prosty jak zjeść ciastko to jednak dorwanie się do danych w chmurze już takie proste nie jest.

I w tym momencie powinienem kopnąć się w łeb i zapomnieć, że śmiałem o tym w ogóle pomyśleć.
Ale przecież w IT nie ma rzeczy niemożliwych...


Dotychczas wyglądało to tak, że synchro calibrioteczki na konto hostingowe robiło się automatycznie po sftp, gdy tylko coś w niej grzebałem a kompjuter przechodził w stan bezczynności. Do przeglądania używałem oprogramowania BicBucStriim (BBS) na desktopie i COPS (Calibre OPDS) na knedlu. COPS posiada konfigurowalną z marszu opcję podania zewnętrznego dostawcy do pobierania książek, więc wydawałoby się, że będzie łatwiej wygenerować linki do pobierania.

Otóż nic bardziej mylnego. Każdy dostawca przechowywania w chmurze uniemożliwił bezpośredni dostęp do plików wymagając wygenerowania linka do pobrania, co i tak przekierowuje do strony pobierania. Tym samym ukrócili możliwość hostowania i streamowania zasobożernych multimediów bezpośrednio na stronach www, twitchu, discordzie czy innym hooyostwie . No cóż, prostotę i fajne ficzery zawsze udupią pazerni kutasiarze.

Tu pojawiły się dodatkowe dwie kwestie/pytania:

  1. Dlaczego nie skorzystać z usług free tier AWS, Oracle czy IBM?
    Bo jest to przerost formy nad treścią, bo działanie, konfigurowanie i zarządzanie tymi usługami jest skomplikowane, nie wspominając o zagmatwanym cenniku tych usług po określonym czasie "darmowości". Personalnie czuję jakiś wstręt i niechęć do próbowania się z tymi usługami czytając wielokrotne płaczki jak to ktoś skorzystał, podał kartę, całkowicie zapomniał o tym a po czasie usługa zaczęła doić środki z karty. Fcuk off!

  2. Przecież wszystko można trzymać na NAS i podpiąć się do niego
    Nie mam NASa. Kupiłem terminal aby przeobrazić go w coś do backupu i udostępniania, ale to jest śpiewka dalekiej przyszłości.

Prosty usecase, proste rozwiązania.

Prosto to się na widły można nadziać.
Zszedł mi cały wieczór na wszelkich kombinacjach. Już myślałem, że przynajmniej do pobierania z dropboxa linki da się wygenerować automatycznie, aby dało się przejść na stronę i z niej pobrać.
Zapomnij synek, byłoby zbyt łatwo i zbyt pięknie.
Mikrozowd aktualizował dostęp do usług już kilka lat temu, czym się w ogóle nie interesowałem, bo z IT to ja już nic wspólnego. Dostęp do nich wymagał stworzenia aplikacji w usłudze azure a potem wykorzystania ich open graph api do dobrania się do zasobów. Poległem na autentykacji oauth2 włącznie z ustawieniami dostępu w konfiguracji aplikacji. Tak oszczanego utrudniania to się nie spodziewałem, choć z zasady powinienem.
Temat zarzuciłem, bo szkoda się z tym pałować nie mając już nawet nadziei, że cokolwiek co zrobię raczy zadziałać zanim święta wielkanocne nadejdą.

Może gógieł drive?
Spróbowałem przerzucić najpierw DB i ze 2 foldery z plikami w przeglądarce. Już na dzień dobry wywalało błędy, zawieszało przeglądarkę itd. Wnet sobie przypomniałem dlaczego nie trzymam w tej dziadowskiej usłudze nawet 1 pliku.

Pomysły się kurczą a rozwiązań .niet

Zamiast kombinować od doopy strony, czas był się rozejrzeć czy nie istnieją jakieś gotowe rozwiązania, albo czy ktoś nie miał podobnego kaprysu.
Dosyć szybko wynalazłem gotowe oprogramowanie pt. RClone służące właśnie do tego, czego potrzebuję. No, ale czy ja w ogóle będę mógł tego użyć na współdzielonym hostingu?
Po wysłaniu maila do pomocy technicznej dostałem lakoniczną informację, że jako użytkownik mogę uruchamiać swoje oprogramowanie oraz potwierdzenie tego co już sprawdziłem tj. że dla freebsd jest dostępna binarka rclone.
Pierwsze dobre wieści (ง •̀ᴗ•́)ง

Tylko jak ja to mam zrobić? FreeBSD w swoim życiu na oczy nie widziałem.
Po jakichś 2 godzinach byłem odrobinę mądrzejszy. Wstyd przyznać, że uruchomienie binarki zabrało mi więcej czasu niżbym się spodziewał. Nie wystarczyło ustawić odpowiednich atrybutów wykonania, mimo wszystko w końcu się udało.
Oczom mym ukazała się pokaźna lista możliwych do skonfigurowania zewnętrznych udziałów, w tym właściwie wszystkich znanych dostawców cloud storage.

BINGO!

rclone storages

Skonfigurowałem dostęp do onedrive i wylistowałem pliki, wszystko zapowiadało się elegancko.
Stwierdziłem jednak, że po wcześniejszych perturbacjach i tym, że wszystko się wlecze w tej chmurze, synchro jest niestabilne i często zrywa połączenie, postawiłem na innego dostawcę.

Jak zrobić synchro na szybciocha?

Do synchro biblioteczki używałem windowsowego softu pt. allway sync. Niestety, został przejęty przez goodsync i z braku aktualizacji synchronizacja zaczęła miewać coraz częściej czkawkę.
No ale przecież nie po to się pałuję z RClone zdalnie, żeby nie skorzystać z niego lokalnie.
Ciach bach pobrane, zainstalowane, skonfigurowane rclone sync x:\blioteczka chmurka: und fertich

Jeszcze tylko zautomatyzować jakiś wyzwalacz coby się samo wywoływało.
Kiedyś na pewno, I promise...

Montowanie udziału

Czas podmontować zewnętrzny udział, wskazać w OPDS-ach katalog z osadzoną biblioteczką i pora na CS-a.

H ci w żopu stary. Niczego nie podmontujesz jako użytkownik.
sysctl vfs.usermount z wynikiem = 0 nie pozostawiło żadnych wątpliwości.
I to by w zasadzie było na tyle, bo grzebanie dalej z fusermount, czy jakimkolwiekmount i tak spełzłoby na niczym.

Ale! RClone umożliwa serwowanie zewnętrznych udziałów m.in. poprzez protokół HTTP.
Już coś cieplej. Jak to jednak ugryźć?
Gdy uruchomię to na interfejsie lokalnym [localhost:PORT] to się do tego nie podepnę z zewnątrz.
W przypadku uruchomienia z nasłuchem na wszystkich interfejsach [0.0.0.0:PORT]:

biblioteka otwarta dla wszystkich, SERDECZNIE ZAPRASZAM!

Dla wszystkich, tj. poprzez każdą osadzoną na tej samej maszynie domenę. Wystarczy tylko znać port.
Szybkie skanowanie - o ile nie jest to zabezpieczone serverside, a tego w zasadzie nie wiem - ujawni zasoby.

Do wyboru mam zatem:

  • Bramkę nr.1 gdzie będę dziergał rozwiązanie z jakimś forwardowaniem czy proxowaniem ruchu, o czym mam nikłe pojęcie.
  • Rozwiązanie nr.2, które jakoś będę próbował zabezpieczyć.

Wypróbowałem na szybko jakieś wynalezione rewrite-y to htaccessa, ale to miało znikome szanse na działanie. Webserwerem jest nginx z modułem apaczowego htaccessa, ale czy mod_proxy czy coś w tym kierunku jest do tego, to ja no tengo ni idea. Po przejrzeniu dokumentacji i możliwościach zabezpieczenia tego stwierdziłem, że jednak nie otworzę biblioteki miejskiej.
Czeknąłem czy jest w ogóle możliwe przekierowanie requesta z zewnątrz do lokalnie działającego webserwera.

BINGO2!

To jest dobry kierunek, sklecę skrypcik w PHP który przyjmie requesta, przekieruje do wewnętrznego zasobu i wypluje pliczek. Takie proste, a ja niepotrzebnie zachodziłem w łeb przez pół wieczoru.
Klepnięty gównokodzik po inauguracji wymagał naniesienia kilku poprawek. Np. zwracany nagłówek http "uszkadzał" plik i trzeba było go usuwać. Pomimo tego, że dostęp jest zabezpieczony używając basic auth, chciałem zabezpieczyć również dostęp do działającego lokalnie webserwera HTTP RClone. Tzw. proof of concept poniżej, jeśli ktoś kiedykolwiek wpadłby na tak postrzelony pomysł jak ja.

<proksy.php>

// Set rclone URL
$rcloneUrl = 'http://localhost:PORT'; //  set right port number

// get requested URI; remove 'proksy.php' from it
$requestUri = str_replace('/proksy.php', '', $_SERVER['REQUEST_URI']);

$proxyUrl = $rcloneUrl . $requestUri;

// initialize cURL session
$ch = curl_init($proxyUrl);
// set cURL options
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
//for autherntication either using htpasswd or rclone launch parameter --user <user> --pass <pass>
curl_setopt($ch, CURLOPT_USERPWD, 'user:pass');
// Execute cURL request
$response = curl_exec($ch);

// get HTTP response code and header size
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

curl_close($ch);

// split response into headers and body
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);

// Send back only relevant headers for download
header("Content-Type: application/octet-stream"); // adjust if necessary for content type
header("Content-Disposition: attachment; filename=\"" . basename($requestUri) . "\"");
header("Content-Length: " . strlen($body));

// output actual file
echo $body;

i voilla
W COPSie wystarczy wskazać jako external storage adres do pliku z powyższym skryptem, np: $config['calibre_external_storage'] = 'https://domain/proksy.php/';

Ta metoda rozwiązała również nurtującą mnie kwestię okładek do książek.
Aby aplikacje OPDS działały, muszą mieć bezpośredni dostęp do pliku z bazą danych metadata.db
Okazuje się, że przez wymodzony skrypt przechodzą również requesty do obrazów okładek i pobieranie z chmury działa na tyle szybko, że nie muszę synchronizować oddzielnie okładek umiejscawiając ich na hostingu. Oczywiście żre to dodatkowo transfer z konta. Niby nieograniczony, ale been there, done that więc jakbym zdoił z 10TB to by się włączył lejek albo ja bym dostał smutną wiadomość i czerwoną kartkę.
Na szczęście, o czym wspomnę dalej, pobrane pliki i obrazy keszuje RClone.
Niemniej zaczęło mnie nurtować czy nie ograniczyć jeszcze w jakiś sposób requestów. Jak coś zacznie odpytywać na chama żądając nieistniejących plików, to niech dostaje odpowiedni nagłówek. Ostatecznie wymodziłem taki oto kawałek gównokodu.
Działa i to jest w tym wszystkim najważniejsze.
A tam, duma mnie rozpiera, że jeszcze coś tam potrafię wymodzić (. ❛ ᴗ ❛.)
Obkomentowałem jak dla początkującego, bo przy kolejnej grzebaninie, czy relokacji nie będę nawet pamiętał o co w tym wszystkim chodziło. Przecież nie dla zainteresowanych, tak jakby poza mną ktoś tu zaglądał ༼ ͡° ͜ʖ ͡° ༽

$rcloneUrl = 'http://localhost:PORT'; // Replace 'port' with your actual port number
// get requested URI and remove 'proksy.php' from it
$requestUri = str_replace('/proksy.php', '', $_SERVER['REQUEST_URI']);
// define  allowed file extensions
$allowedExtensions = ['jpg', 'png', 'mobi', 'epub', 'azw3', 'pdf', 'txt','doc','docx','rtf'];
// get file extension from the request URI
$fileExtension = pathinfo($requestUri, PATHINFO_EXTENSION);
// Check if the file extension is allowed
if (in_array($fileExtension, $allowedExtensions)) {

    $ch = curl_init($rcloneUrl . $requestUri);
    // Set cURL options
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    //for autherntication either using htpasswd or rclone launch parameter --user <user> --pass <pass>
    curl_setopt($ch, CURLOPT_USERPWD, 'user:pass');

    // Execute cURL request
    $response = curl_exec($ch);

    // get HTTP response code and header size
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    // Close cURL session
    curl_close($ch);

    // Check if the file exists (HTTP status code 200)
    if ($httpCode == 200) {
        // Split response into headers and body
        $headers = substr($response, 0, $headerSize);
        $body = substr($response, $headerSize);

        // Send back only relevant headers for download
        header("Content-Type: application/octet-stream"); // Adjust as necessary for your content type
        header("Content-Disposition: attachment; filename=\"" . basename($requestUri) . "\"");
        header("Content-Length: " . strlen($body));
        // Output the body of the response (the actual file)
        echo $body;

    } else {
        // If the file does not exist, return a 403 Forbidden response
        header('HTTP/1.1 403 Forbidden');
        echo '403 Forbidden: Try requesting this file from your a-hole';
    }

} else {
    // If the file extension is not allowed, return a 403 Forbidden response
    header('HTTP/1.1 403 Forbidden');
    echo '403 Forbidden: sorry mate, no pr0n here :(';
}

Tym sposobem jestem trochę spokojniejszy.

COPS łączy się z bazą, pliki książek da się pobrać, okładki się wyświetlają.
Nadszedł czas na konfigurację BBS. Ten niestety nie oferuje tak prostego przekierowania jak COPS. Grzebanie w funkcjach spełzło na niczym, bo jest tego ogrom a nie mam nad tym ochoty siedzieć przez tydzień.
Coś mnie jednak tknęło żeby zerknąć do szablonów.

Przeklęty TWIG. Nie dość, że się z PHP muszę pałować, choć od dawna nie mam zamiaru mieć z tym nic wspólnego, to jeszcze przyjdzie dłubać w gównokodzie systemu szablonów.
Ehhh... (⇀‸ლ)

Long story short

W najprostszym z możliwych rozwiązań, żeby nie tracić już więcej czasu ślęcząc nad tym trzeci dzień, trzeba poprawić generowanie linków do książek oraz okładek w ustawionym w BBS szablonie.

<title_detail.twig>

>URLs:

<a data-role="button" data-ajax="false" 
   download="{{format.name|url_encode()}}.{{format.format|lower}}"
   href="{{page.rot|replace({'/BBS': ""})}}/proksy.php/{{book.path}}/{{format.name|url_encode()}}.{{format.format|lower}}">
        {{ format.format }} | {{format.uncompressed_size|hfsize}}
</a>

>COVER IMG:

<aside class="title_detail_image">
    {% if book.has_cover %}     
    <img src="{{page.rot|replace({'/BBS': ""})}}/proksy.php/{{book.path}}/cover.jpg" />
    {% endif %}     
</aside>
<title_entry.twig>

<img src="{{page.rot|replace({'/BBS': ""})}}/proksy.php/{{book.path}}/cover.jpg" />

Może nie jest to kuloodporne rozwiązanie, ale kto robi dobrze? #pdk


Finale grande

Nie było okazji wspomnieć o tym wcześniej, a same odpalenie RClone i jego działanie w trybie permanentnym jest tu istotne.

nohup rclone serve http  <chmurka>: --addr localhost:PORT --dir-cache-time 72h --poll-interval 24h --buffer-size 0 --vfs-cache-mode full --no-modtime --no-traverse --htpasswd /.htpasswd  > ./nohuprclone.log 2>&1 &

Musiałem w jakiś sposób uruchomić to tak, by działało w tle po rozłączeniu sesji. Z pomocą przychodzi nohup ("no hang up") umożliwiające działanie w tle po wylogowaniu użytkownika, przerwaniu sesji itp. Możliwym rozwiązaniem jest jeszcze użycie screen, ale to wymaga dużo więcej zachodu. Aczkolwiek gdyby się nohup nie sprawdził do tego, to już jakiś punkt wyjścia w przyszłości:

screen -dm bash -c "localhost:PORT"

W każdym razie katalogi siedzą w cache przez 72h zanim zostaną odświeżone, z vfscachemode full wszystko do czego poszedł request będzie już siedziało lokalnie w cache. Poll interval sprawdzi co 24h czy wystąpiła jakaś aktualizacja. Buffer size już nie pamiętam do czego służył, nie chce mi się wracać do dokumentacji. Pewnie przyspieszał odczyt. Ten z pewnością jest szybszy dzięki nomodtime. Końcówka to zabezpieczenie dostępu poprzez użytkownika i hasło w pliku .htpassword, który miałem już wcześniej utworzony do innych celów.

Gwoli dopełnienia, bo rozruch jest już jasny, wypadałoby jeszcze podać jak zrobić killim serwera HTTP RClone.
Można próbować przy rozruchu zapisać do zmiennej PID procesu, aby potem oczywiście zapomnieć użytej zmiennej. Listujemy zatem procesy przy użyciu ps, top, htop, atop czy jaki tam *top jest dostępny.

Najlepsze jest PS bo wyświetli listę procesów i się zamknie.
Z użytym parametrem "r" wyświetli wszystkie działające procesy, a "wwf" lub "auxfww" zawinie długie linie w małym oknie shella. W zasadzie to i bez parametru wyświetli co potrzeba, czyli PID procesu, któremu chcemy zrobić killim.
Odpalamy procedurę no.9 i elo.

kill -9 <PID>

Pozostaje jeszcze zautomatyzowanie rozruchu, gdyby się serwer wyj$%*&ł na ryj.
Co ja mogę jako zwykły użytkownik?
Dodać regułkę w crontabie. O, tyle mogę!

pkill rclone && nohup rclone serve http <chmurka>: --addr localhost:PORT --dir-cache-time 72h --poll-interval 24h --buffer-size 0 --vfs-cache-mode full --no-modtime --no-traverse --htpasswd /.htpasswd > ./nohuprclone.log 2>&1 &

Komenda identyko jak przy uruchomieniu do działania w tle. Różnica taka, że jeśli serwer działa to restartujemy go raz w tygodniu (0 0 * * 0) .


Posłowie

To chyba tyle z tej postrzelonej przygody. Wymyśliłem sobie usecase, którego rozwiązanie zabrało mi ponad 3 dni.
Ambicja nie pozwoliła mi jednak rzucić tym w pierony. Ma się rozumieć że nie 3 dni z rzędu, bo mnie szlag trafiał już po pierwszym dniu grzebania przy tak prostej, wydawałoby się, rzeczy.
Chciałbym postawić docelowo ten majdan na terminalu przeobrażonym w domowy miniserwer. Przy użyciu usług CloudFlare powinno to być wystarczająco bezpieczne i stabilne, nawet na dynamicznym IP.

Gorzki niesmak czuję po tym, że musiałem srać się aż tyle z PHP. Nie mam ochoty na programowanie, odpuściłem lata temu. Teraz to nawet sensowne pieniądze proponują za rzeźbienie w tym szajsie, w porównaniu do tego jak żaden był rynek 10 lat temu. Mogłem też spróbować z oprogramowaniem pisanym w innym języku. Zaświtało mi to w tym pustym łbie dopiero gdy już odetchnąłem po wszystkim. Przecież MyDevil umożliwia uruchamianie oprogramowania stworzonego m.in. w Go, pythonie i jeszcze kilku innych, a przypomniałem sobie, że kilka lat temu znalazłem jakiegoś OPDSa stworzonego w GO.
Najlepsze pomysły zazwyczaj po zakończeniu roboty (⇀‸ლ)


EDYP:1

Nystyty, sam crondżob nie wystarczył do utrzymania działalności erklona w tle.
Ponieważ nie spuszczałem się zbytnio nad tym, toteż jakikolwiek.log był całkowicie pusty.
Za mną kolejna lekcja przypominania sobie skryptowania w bashu, po której wymodziłem poniższy gównokod:

<startclone.sh>

SHELL=/usr/local/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/home/UZER/bin
# Check if program already running
#if ps -wwf | grep "rclone" > /dev/null
if pgrep -f "rclone" > /dev/null
then
   echo "rclone already running."
else
   echo "launching rclone"
   nohup rclone serve http <chmurka>: --addr localhost:PORT --dir-cache-time 72h --poll-interval 24h --buffer-size 0 --vfs-cache-mode full --no-modtime --no-traverse --htpasswd /.htpasswd > ./nohuprclone.log 2>&1 &
fi

Na dzień dobry oberwałem syntax error near unexpected token fi
Szkrw, windowsowe line endings w pliku (⇀‸ლ)
Podejrzewałem jeszcze, że pgrep może nie wyświetlać oczekiwanych danych wyjściowych, stąd zakomentowane ps
Przynajmniej o chmod +x krypcior.sh pamiętałem <( ̄ ︶  ̄)>
Pozostała edycja crona. Żeby nie było za łatwo, edytować się nie dało. W nowym wpisie ustawiłem odpalanie skryptu co 6h.

30 */6 * * * /home/UZER/bin/startclone.sh 2>&1 &

Działa?
DZIAŁA!

Obaczym ile tym razem podziała.


EDYP:2

Zakiełkowała we mnie natrętna myśl czy wyświetlenie statusu instancji erklona na splashscreenie wyboru COPS/BBS nie byłoby przydatne. Wiedziałbym od razu czy instancja padła i jest sens czekać aż się sama podniesie, czy jednak bez ręcznego odpalenia po połączeniu po ssh się nie obejdzie. Skonstatowałem przy tym, że wcześniejsze manewry z przerzuceniem rozruchu spod crona do skryptu w bashu to był dobry ruch. Kod z przygotowanego wcześniej skryptu do proksowania przydał się i tutaj, dzięki czemu realizacja zajęła dosłownie kilka minut. Koncept polega na sprawdzeniu, czy wywołanie przerzuconego do < chmurki > obrazeczka zwróci status http 200. Jeśli tak to wsio ok, można doić lektury.

$imageUrl = 'http://localhost:port/img.png';
// Initialize cURL session; set options
$ch = curl_init($imageUrl);
curl_setopt($ch, CURLOPT_NOBODY, true); // We don't need the body
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Don't output the response
curl_setopt($ch, CURLOPT_HEADER, true); // Include the header in the output
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
curl_setopt($ch, CURLOPT_USERPWD, 'USER:PASS');
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// Close cURL session
curl_close($ch);
if ($httpCode == 200) {
    echo " 👍 up&running ";
} else {
    echo " 👎 tango down ";
}

Choć nikłe to jednak mam nadzieje, że już więcej nie będę w tym grzebał, ani tym bardziej nie wpadnę na kolejny durnowaty pomysł związany z całym tym ambarasem.

AMENT!