augimeri
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Migrazione delle Piattaforma Geonode SDI di Techfem La seguente documentazione descrive le opertazioni di base per predisporre un ambiente di esecuzione per la piattaforma **Techfem SDI** per la condivisione di dati geospazioani aziendale. La piattaforma è basata sul progetto [GeoNode](https://geonode.org/), soluzione Open Source per implemenetare sistemi GIS (Geospatial Information Systems) e SDI (Spatial Data Infrastructures) ed è stata inizialmente sviluppata e personalizzata da [Geodatalab S.r.l](https://www.geodatalab.it/). Di seguito vengono riportate le operazioni principali per migrare o reinstallare il sistema da zero, su un host dedicato on-premises o su infrastruttura cloud proprietaria, partendo dal codice sorgente del progetto. Per informazioni dettagliate sull'utilizzo e sulle funzionalità della piattaforma si rimanda invece alla documentazione ufficiale. Le istruzioni fornite sono relative a sistemi in ambiente GNU/Linux, architettura x86_64 (64 bit) e sistema operativo Ubuntu 18.04 LTS o successivo con Linux kernel dalla versione 5.10 in poi e supporto ai sistemi di containerizzazione basati su [Docker](https://www.docker.com/). Tuttavia, le procedure di configurazione dovrebbero essere facilmente adattabili anche ad altri sistemi Debian-based o sistemi basati su CentOS a patto di predisporre i pacchetti con le dipendenze minime di Geonode (vedi [documentazione](https://docs.geonode.org/en/master/install/basic/index.html)). ## Accesso SSH all'istanza Ipotizzando di aver già avviato l'host di destinazione della piattaforma dalla console di gestione della propria area personale sul provider cloud, è necessario instaurare la prima connessione alla macchina. In base al servizio scelto, è possibile che sia disponibile una applicazione web di amministrazione remota dell'host per gestire ogni aspetto del sistema. Le più comuni sono: * [CPanel](https://cpanel.net/) * [Webmin](https://webmin.com/) * [Plesk](https://www.plesk.com/) Ad ogni modo, anche in presenza di quesi strumenti, si consiglia di procedere all'accesso utilizzando il protocollo SSH (Secure Shell) a riga di comando. Per farlo è necessario disporre di un client SSH sul proprio pc per connettersi all'host remoto in cloud. Sui sistemi GNU/Linux è quasi sempre presente il client SSH di OpenSSH già preinstallato. Lo stesso avviene per MacOs. Aprire il proprio terminale e verificare la presenza del client digitando il seguente comando: ```shell ssh -V ``` I sistemi Windows dalla versione 10 intetrano un client nella *PowerShell*. Per quelli meno recenti si pu usare [PuTTY](https://www.putty.org/) Se il client è disponibile, per instaurare la connessione all'host, la sintassi è la seguente: ```shell ssh user@host ``` *User e Host sono forniti dal provider* Confermare digitando `yes` (solo la prima volta) e quindi la password di accesso per l'utente, di solito fornita dal provider del cloud. Se la connessione ha successo, il terminale visualizza il prompt dell'host remoto ed è possibile procedere con la guida. ### Configurazione dell'accesso tramite chiave pubblica/privata L'autenticazione dell'utente tramite SSH può avvenire in diversi modi. Per connessioni sporadiche o per i primi accessi è facile ricorrere all'autenticazione tramite password. Tuttavia, questa modalità è sconsigliata per diversi ragioni: se non viene disabilitata, espone l'host remoto e il server SSH ad attacchi di tipo Brute force; La richiesta interattiva di inserimento password ad ogni accesso impedisce anche le automazioni più semplici. Per superare queste limitazioni, configurare l'accesso per l'utilizzo di una chiave pubblico/privata e disabilitare l'autenticazione basata su username/password. *Le istruzioni seguenti presuppongono l'uso del client OpenSSH su GNU/Linux o su MacOS* Generare una coppia di chiavi sul proprio host digitando il comando seguente e accettando i valori di predefiniti: ```shell ssh-keygen -t RSA ``` il comando genera due file nella home utente | Path | Key | | --------------------- | ------------ | | `~/.ssh/id_rsa` | Private key | | `~/.ssh/id_rsa.pub` | Public key | La chiave pubblica (e solo la chiave pubblica) deve essere trasferita anche sull'host remoto con il comando: ```shell ssh-copy-id -i ~/.ssh/id_rsa.pub user@host ``` Ciò aggiunge la chiave all'elenco delle chiavi accettate dal server SSH cui ci si vuole loggare (`~/.ssh/authorized_keys`) A questo punto dovrebbe essere possibile loggarsi senza che venga richiesta la password e a patto che la chiave privata sia nella directory `~/.ssh`. Se il login funziona, disabilitare l'autenticazione tramite password sul server SSH sull'host (si veda la documentazione ufficiale `man ssh`). ### Usare il file Config di SSH Se ci si collega di frequente o si devono gestire molti server è possibile configurare i propri host preferiti per l'accesso rapido e richiamarli con un nome mnemonico, aggiungendo una sezione al file `~/.ssh/config`. Aprire il file con un qualunque editor di testo ed appendere le seguenti righe: ```shell= Host mycloud ## assegnare un nome corto e semplice HostName <host-ip-address> User <user> ``` Come `host-ip-address` si può usare sia l'indirizzo IP pubblico o il nome host dns pubblico, entrambi reperibili dalla console del provider. A questo punto è possibile accedere al proprio server in modo rapido e senza password ```shell ssh mycloud ``` ### Disabilitare l'accesso SSH per Superuser (root) Alcuni provider forniscono al primo accesso solo sistemi in cui esiste il solo utente **root**, che quindi è l'unico a poter essere utilizzato per accedere all'host in SSH almeno nelle prime connessioni. Per questioni di sicurezza, è fortemente sconsigliato usare l'utente root per le normali operazioni di gestione. La cosa migliore da fare, già al primo accesso, è quella di aggiungere un utente convenzionale al sistema, eventualmente concedere a quest'ultimo i privilegi di amministratore (vedi il seguito) e usare queste credenziali per le sessioni successive. La possibilità di loggarsi come root direttamente in SSH potrebbe poi essere disabilitata sul server (vedi `man ssh` per ulteriori dettagli). Così facendo si ha un margine di sicurezza migliore ed una futura separazione dei privilegi gestibile, nel caso si debba condividere le credenziali a terzi. ## Creazione di un utente con login dedicata alla piattaforma Come anticipato, è consigliabile creare un utente dedicato per la gestione della piattaforma ed eseguire le sucessive operazioni tramite un sessione apposita. Ipotizzando di essere loggati come `root` sull'host remoto, creare un nuovo utente: ```shell= adduser sdiadmin ``` creare una password per il login ```shell= passwd sdiadmin ``` quindi aggiungerlo al grupopo dei *sudoers* per concedere i dirutti di superutente: ```shell= usermod -a -G sudo sdiadmin ``` È ora possibile disconnette la sessione corrente e ricollegardi con l'utente appena creato: ```shell= ssh sdiadmin@host ``` Configurare il proprio file `~/.ssh/config` per un accesso agevolato come indicato [precedentemente](#Usare-il-file-Config-di-SSH) e seguire anche per questo utente il [paragrafo per l'accesso con chiave pubblico/privata](#Configurazione-dellaccesso-tramite-chiave-pubblicaprivata). Mettere quindi in sicurezza il server SSH secondo le proprie preferenze anche seguendo i consigli riportati [qui](#Disabilitare-laccesso-SSH-per-Superuser-root). ## Istallazione e configurazione di Docker Questa parte riprende la documentazione ufficiale per l'installazione di Docker in Ubuntu Linux, disponibile al seguente [link](https://docs.docker.com/engine/install/ubuntu/) Per il deploy della piattaforma sono necessari almeno i seguenti componenti dell'ecosistema Docker: 1. Docker Engine - ambiente di containerizzazione 2. Docker Compose - strumento per la gestione di immagini e container e riga di comando Le istruzioni si riferiscono ad Ubuntu 22.04 LTS, ma dovrebbero funzionare anche per versioni adiacenti. Prima di installare per la prima volta su un nuovo host, effettuare il set up dei repository Docker ```shell= # Add Docker's official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update ``` Istallare i pacchetti dell'ultima versione ```shell= sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` e verificare che tutto sia andato istallato correttamente, lanciando un container di prova ```shell= sudo docker run hello-world ``` ### Docker Engine Post-installation steps Le seguenti operazioni sono necessarie per configurare al meglio Docker in ambiente Linux. #### Gestire Docker da un utente non-root Per consentire ad un utente non-root di gestire le operazioni di containerizzazione creare un gruppo `docker` digitando: ```shell= sudo groupadd docker # il group potrebbe essere già presente ``` quindi aggiungere gli utenti desiderati: ```shell= sudo usermod -a -G docker sdiadmin ``` Gli utenti coinvolti devono eseguire riloggarsi per consentire al sistema di aggiornare la membership. Verificare quindi con il comando: ```shell= docker run hello-world ``` #### Avviare Docker al boot con Systemd Molte distribuzioni Linux moderne usano [systemd](https://systemd.io/) per gestire quali servizi debbano avviarsi in automatico al boot del sistema. Su Debian e Ubuntu, Docker si avvia al boot di default. Per ottenere lo stesso comportamento su altre distribuzioni usando systemd, eseguire i seguenti comandi: ```shell= sudo systemctl enable docker.service sudo systemctl enable containerd.service ``` mentre per ottenere l'opposto usare: ```shell= sudo systemctl disable docker.service sudo systemctl disable containerd.service ``` #### Configurare il comportamento dei logs Docker produce diversi file di log e salva sull'host il logs di tutti i container in formato json. Queste funzioni spesso comportano un consumo eccessivo di spazio su disco, soprattutto nei casi in cui lo spazio assegnato ai servizi in cloud non è sufficiente. Per risolvere il problema si può consuare la seguente [guida ufficiale](https://docs.docker.com/engine/install/linux-postinstall/#configure-default-logging-driver) che offre una soluzione per ogni esigenza. ## Codice sorgente Se l'istallazione di Docker è andata a buon fine è possibile procedere con la fase di setup e deploy della piattaforma. Copiare il progetto sull'host tramite Git o spostando il paccketto dei sorgenti dalla propria postazione. ### SCP SCP è un semplice strumento per il trasporto di file e directory tramite SSH. Generalmente il comando è già presente nel terminale perché incluso nel client OpenSSH. Verificare digitando ```shell! scp -v ``` Per copiare l'archivio con i sorgenti (es. `j` dal proprio host all'host remoto digitare ```shell! scp geonode-sdi-dev.zip sdiadmin@host:/tmp ``` quindi collegarsi all'host e scompattare l'archivio in una direcotry adeguata all'istallazione (es. `/opt`) ```shell= sudo unzip -d /opt /tmp/geonode-sdi-dev.zip cd /opt mv geonode-sdi-dev geonode-sdi # opzionale ``` ### GIT Clone Verificare che il client di Git sia istallato: ```shell= git --version ``` spostarsi nella directory prescelta e clonare ```shell= cd /opt git clone git@github.com:Techfem-spa/geonode-sdi.git ``` ### Re-init e migrazione del repository Ipotizzando di avere la piattaforma ben configurata e qualora si dovesse re-inizializzare il repository e/o spostarlo su una nuova sorgente remota (ad esempio un nuovo account Github) è possibile procedere nel seguente modo: ```shell= cd /opt/geonode-sdi rm -rf .git # rimuove l'attuale configurazione di git ``` assicurarsi che la folder adesso non sia più vista da Git come un repo ```shell= git status # deve restiruire un FATAL error ``` quindi procedere con una nuova *init* e aggiungere tutti i file ```shell= git init git add . # da eseguire in /opt/geonode-sdi git status # verificare che il tracking sia attivo ``` eseguire il primo commit e rinominare il branch principale in `main` ```shell= git commit -m "First commit" git branch -M main git status # verificare che il working tree sia pulito ``` settare l'url del repo `origin` per upstream e downstream e pushare il commit verso di esso ```shell= git remote add origin https://github.com/Techfem/geonode-sdi git push origin main ``` Si noti che, se il repo è gestito sulla piattaforma Github, in caso di repository privato non sarà possibile utilizzare http/https come protocollo di trasporto per le operazioni di `push`, `pull`, ecc. Consulatere la [documentazione ufficiale di Github](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) LINK per configurare le chiavi di sicurezza tra il server ed un account account Github che possa agire sull'organizzazione. --- ## Deploy di Geonode ### 1. Customizzazione del file .env Modificare le variabili del file `.env` puntando all'ip o al dominio corretto: ```shell= GEONODE_LB_HOST_IP=sdi.techfem.it SITEURL=https://sdi.techfem.it/ #aggiungere l’host in allowed_hosts ALLOWED_HOSTS=['django', 'localhost', 'sdi.techfem.it', ...] HTTP_HOST=sdi.techfem.it HTTPS_HOST=sdi.techfem.it GEOSERVER_WEB_UI_LOCATION=https://sdi.techfem.it/geoserver/ GEOSERVER_PUBLIC_LOCATION=https://sdi.techfem.it/geoserver/ ``` ### 2. Docker start Lanciare il build delle immagini docker e far partire l'istanza di Geonode. ```shell= docker-compose -f docker-compose.yml up --build -d ``` ### 3. Docker rebuild In caso di cambiamenti nei parametri (ad esempio nei file .env), è necessario fermare e container e effettuare il rebuild. Il comando 'down' rimuove i container dopo averli fermati. ```shell= docker-compose -f docker-compose.yml -f docker-compose-bckup.yml down ``` Per il rebuild lanciare il seguente comando, dove `-d` lancia in *detached-mode* ```shell= docker-compose -f docker-compose.yml -f docker-compose-bckup.yml up -d ``` ## Setup "GeoNode Mapstore Client" (Non utilizzato nel deploy attuale) E' innanzitutto necessario avere un'istanza di Geonode avviata. #### 1. Struttura cartelle La struttura delle cartelle potrebbe essere. ``` + opt/ - geonode - mapstore-client ``` #### 2. Clone del Repository Fare il clone del repository nel proprio workspace. `git clone --recursuve git@gitlab.com:geodatalab-geonode/techfem/geomapstore-client.git mapstore-client` #### 3. Far puntare l'installazione di Geonode al fork del mapstore-client Per fare questo è necessario modificare il 'requirements.txt' sotto `/opt/geonode`. In particolare, questo requisito: ``` #GeoNode org maintained apps. django-geonode-mapstore-client==3.2.1 ``` diventa: ``` #GeoNode org maintained apps. -e git+https://pmimapstore:kdmLnLp3x1nw9ZLUvHJH@gitlab.com/geodatalab-geonode/techfem/geomapstore-client.git@master#egg=django_geonode_mapstore_client ``` Dove l'url del repo è proceduto da un token generato appositamente. ## BACKUP-RESTORE (DA VERIFICARE) ### 1. BACKUP (Daily) #### SETUP The backup use two containers that are defined as docker-compose overwrite `docker-compose-bckup.yml`: - schedular : `mcuadros/ofelia:latest` - uploader : based on alpine with aws cli The change on the env ``` AWS_ACCESS_KEY_ID=b90ebf6e3d024c8786230a142436272d AWS_SECRET_ACCESS_KEY=ad54b90089d31329ac953af3fed8df73 AWS_BUCKET_NAME=backup AWS_PATH=techfem ``` **AWS_PATH** should be the name of the directory more detail about the upload script can be seen [uploadtos3.sh](script-backups/uploadtos3.sh) #### RUN THE PROJECT IN PRODUCTION ```docker-compose -f docker-compose.yml -f docker-compose-bckup.yml up -d``` ### 2. RESTORE a. Download tar from s3 b. Extract ``` data datadir geolib geonode_data.sql geonode.sql uploaded ``` c. restore db ```bash docker-compose -f docker-compose.yml up -d db ``` ``` docker cp geonode.sql db4geonode:/tmp/ docker cp geonode_data.sql db4geonode:/tmp/ docker exec -it db4geonode psql -U postgres -d geonode -f /tmp/geonode.sql docker exec -it db4geonode psql -U postgres -d geonode_data -f /tmp/geonode_data.sql ``` d. Copy volumes ``` datadir -> /var/lib/docker/volumes/geonode-gsdatadir/_data data -> /var/lib/docker/volumes/geonode-data/_data uploaded -> /var/lib/docker/volumes/geonode-statics/_data (the folder not the content) geolib -> /var/lib/docker/volumes/geonode-glib/_data ``` e. The above backup does copy the followings: | Container | File | Type | |-------------------|---------------------|-----------| | db4geonode | geonode.sql | sql | | db4geonode | geonode_data.sql | sql | | django4geonode | uploaded | directory | | geoserver4geonode | geoserver_data/data | directory | | geoserver4geonode | data | directory | | geoserver4geonode | lib | directory | #### NOTE (per migrazione originale) - Before restoring the database as only for this project we need to create a new ROLE called *qgis_data* - Be sure this user has all the privileges on schemas qgis_data and public. ```sql GRANT ALL PRIVILEGES ON SCHEMA qgis_data TO qgis_data; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA qgis_data TO qgis_data; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA qgis_data TO qgis_data; GRANT ALL PRIVILEGES ON SCHEMA public TO qgis_data; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO qgis_data; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO qgis_data; ``` A procedure calculates, given a layer, the list of permissions needed for that area. Set the following role to make this procedure fully work. ```sql SET ROLE geonode_data; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO qgis_data; ``` - For "Direzione Lavori", a new ROLE called *dlproc* should be created and it should have access to dlproc schema. ```sql GRANT USAGE ON SCHEMA dlproc TO dlproc; GRANT SELECT ON ALL TABLES IN SCHEMA dlproc TO dlproc; ``` ## Installazione PIMGIS PLUGIN Aggiungere le info per scaricare l'ultima versione del zip contente il plugin

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully